Swift 内存管理(一)
首先来介绍什么是内存管理,内存管理是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。内存管理这一概念在iOS开发与安卓开发中都尤为的重要。
在iOS开发中内存管理主要管理的方式根据语言的不同有所区别,先说Objective-C语言,内存管理方式分为:MRC,ARC两种方式。内存管理的原理是引用计数。
这里简单介绍一下引用计数的概念:苹果公司在设计语言的时候,在对象内部设置了一个计数器,当使用new,alloc等关键字创建一个对象时,系统会为这个对象开辟内存空间,这个计数器会自动加一。当引用计数为零的时候系统会回收这个对象所占用的内存。
MRC,是手动内存管理。也就是说,主动创建的对象,要手动释放对象的引用计数,即release。使用release方法会使对象的引用计数减一。
ARC,是自动内存管理,也就是主动创建的对象,系统会自动检测对象是否还有引用指向这个对象,如果没有,那么系统会回收这个对象。
针对Swift来说内存管理并没有OC那么多说道,Swift只有ARC一种方式。不过原理还是引用计数。
析构方法
析构方法的自动继承
Swift语言的内存管理分析
弱引用
循环强引用
unowned 解决循环强引用
1.析构方法
对象的内存被回收前会被隐式调用,主要用来执行一些额外的操作,比如:关闭文件,断开网络等释放对象持有的一些资源等。等同于oc中的-(void)dealloc方法一样。在对象被销毁的时候被隐式调用。
语法是
deinit{ 代码块 }
代码演示
class FileHandler {
var fd : Int32? //文件描述符
init(path :String) {
let ret = open(path, O_RDONLY)
if ret == -1 {
fd = nil
}else{
fd = ret
}
}
<!--析构方法-->
deinit {
if let oft = fd {
close(oft)
}
print("对象被销毁")
}
}
<!--对象何时被销毁,对象没有任何引用的时候,只要是Mac路径肯定会有“/etc/passwd”-->
var file : FileHandler? = FileHandler(path : "/etc/passwd”)
<!--如果没有将file指向nil那么对象将不会被销毁-->
file = nil
打印信息为
对象被销毁
2.析构方法的自动继承 - 父类的析构方法会被自动调用,不需要子类管理
代码演示
class Father {
deinit {
print("fatherClass deinit")
}
}
class SonClass : Father {
deinit {
print("sonClass deinit")
}
}
var sone : SonClass? = SonClass()
<!--当sone变量指向nil之后,对象会被回收,自动调用deinit方法-->
sone = nil
打印信息
sonClass deinit
fatherClass deinit
3.Swift语言的内存管理分析
内存管理的对象 - 被管理的是引用类型的对象(class类型),值类型是不需要管理的
内存管理的原则:当没有任何引用指向某个对象的时候,系统会自动销毁该对象
如何做到的原理:ARC
代码演示
class MemArr {
deinit {
print("deinit")
}
}
var t0 = MemArr()
var t1 = t0
var t2 = MemArr()
<!--t0指向t2,t1也指向t2,那么t1原来的指向对象会被回收-->
t0 = t2
t1 = t2
打印信息
deinit
4.弱引用
weak字符串修饰的即为弱引用对象,并不会引起引用计数增加,当对象被释放(回收)之后,weak修饰的变量将指向nil,weak引用是一种非常安全的引用方式。ps:weak修饰的类型一定是个可选值类型
unowned字符串修饰的也为弱引用对象,但是与weak不同的是,unowned不允许设置为可选值类型,也不允许有nil值的情况。之前的Swift版本,unowned修饰的对象可以正常书写,只有在运行时才会崩溃报错,在3.0版本编译器会检测其修饰的对象是否为nil。
代码演示
1. weak使用
class Ref {
deinit {
print("ref deinit")
}
func test() {
print("test")
}
}
<!--强引用,引用计数加一-->
var ref = Ref()
<!--弱引用-->
weak var weafRef = Ref()
<!--下面的代码与上面的等效,上面的缺省隐式类型-->
<!--`weak var weafRef1 : Ref? = Ref()`
此处做的类型定义为可选值类型-->
<!--这里采用隐式解包,
因为在使用weak修饰的对象可能返回一个nil,
所以类型会是缺省类型-->
if let red = weafRef {
<!--因为对象已经被回收所以方法不会被执行-->
red.test()
}
2. unowned的使用
class Ref {
deinit {
print("ref deinit")
}
func test() {
print("test")
}
}
<!--会报错,信息为:”Execution was interrupted, reason : EXC_BREAKPOINT *****“-->
unowned var unowned :Ref = Ref()
unowned.test()
打印信息
1. weak的使用
ref deinit
2. unowned的使用
<!--没有打印信息-->
5. 循环强引用
ARC不是万能的,虽然他可以很好的解决内存过早释放的问题,但是在某些场合下不能很好的解决内存泄漏的问题
代码演示问题
class Person {
let name : String
init(name : String) {
self.name = name
}
var apartment : Apartment?
deinit {
print("\(name) ")
}
}
class Apartment {
let number : Int
init(number : Int) {
self.number = number;
}
var tenant : Person?
deinit {
print("apartment \(number) is being deinit")
}
}
var john : Person?
var number73 :Apartment?
john = Person(name: "john")
number73 = Apartment(number: 73)
john!.apartment = number73
number73!.tenant = john
//指向了nil,所以变量指向的对象已经被回收,但是都是强引用,
//互相的强引用导致产生了内存泄漏
john = nil
number73 = nil
想要的是将对象都释放掉,并且有打印信息,可是打印信息并没有,原因通过图片演示
john持有Person类的强引用,number73持有Apartment类的强引用,然后在Person类中有属性apartmrnt又指向number73,而Apartmrnt类中也有属性tenant指向john,这就造成了对象之间的互相引用,当john跟number73都指向了nil之后,john与number73都释放了对对象的持有关系,但是Person类与Apartment类却又有互相之间的持有强引用,如下图所示
这就造成了循环引用,导致内存泄漏,虽然对象之间还互相持有强引用,但是原本持有对象强引用的john与number73,已经释放了对对象的引用,简言之,我们无法通过任何东西再访问到Person类和Apartemnt类。
解决办法
将任一对象中的属性用所引用来修饰,使其中一个对象原本的强引用变成弱引用,这样就不会造成循环引用了。
代码演示
class Person {
let name : String
init(name : String) {
self.name = name
}
var apartment : Apartment?
deinit {
print("\(name) person is deinit")
}
}
class Apartment {
let number : Int
init(number : Int) {
self.number = number;
}
<!--将该属性设置为weak-->
weak var tenant : Person?
deinit {
print("apartment \(number) is being deinit")
}
}
var john : Person?
var number73 :Apartment?
john = Person(name: "john")
number73 = Apartment(number: 73)
john!.apartment = number73
number73!.tenant = john
john = nil
number73 = nil
打印信息
john person is deinit
apartment 73 is being deinit
通过打印信息可知,对象已经被回收,互相引用问题已被解决
此图表示了改进代码之后的引用关系,两个类对象之间的相互调用不在是强引用。当john指向nil之后,其指向的Person对象也会被回收,如下图
随后number73也指向了nil,其指向的对象Apartment也也会被回收,如下图
6. unowned 解决循环强引用
代码演示
class Customer {
let name : String
var card :CreditCard?
init(name : String) {
self.name = name
}
deinit {
print("\(name) is being deinit")
}
}
class CreditCard {
let number : UInt64
let customer : Customer
init(number : UInt64, customer : Customer) {
self.number = number;
self.customer = customer
}
deinit {
print("Card #\(number) is being deinit")
}
}
var jeson : Customer?
jeson = Customer(name: "jeson")
jeson!.card = CreditCard(number: 123456789, customer: jeson!)
<!--释放持有对象-->
jeson = nil
打印信息
<!--没有打印信息-->
由于两个类中的存储属性都对互相之间有强引用,造成内存泄漏,对象没有被回收
解决办法
将任一类对象的存储属性,使用unowned来修饰,不过unowned并不能修饰可选值类型,所以unowned就会用来修饰非可选值类型的属性。
修改部分
class CreditCard {
let number : UInt64
<!--此处unowned只能修饰非可选值类型属性-->
unowned let customer : Customer
init(number : UInt64, customer : Customer) {
self.number = number;
self.customer = customer
}
deinit {
print("Card #\(number) is being deinit")
}
}
修改此处之后,上面的代码输出将会如下
打印信息
jeson is being deinit
Card #123456789 is being deinit
扫描二维码
关注更多精彩
点击“阅读原文”